package com.yuzhihao.writejvm.attributes;

import com.yuzhihao.writejvm.clazz.ClassInfo;
import com.yuzhihao.writejvm.constant.ConstantClassInfo;
import com.yuzhihao.writejvm.constant.ConstantUtf8Info;
import com.yuzhihao.writejvm.format.PrintFormat;
import com.yuzhihao.writejvm.read.Parse;
import com.yuzhihao.writejvm.read.TypeFatory;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.log4j.Log4j2;

import java.io.DataInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * 内部类
 * <a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.6">InnerClassesAttribute</a>
 *
 * @author yzh
 * @since 2021年12月11日11:17:46
 */
@Data
@Log4j2
@EqualsAndHashCode(callSuper = true)
public class InnerClassesAttribute extends Attributes {

    private int number_of_classes_u2 = 2;

    private int number_of_classes;

    private List<Inner> inners = new ArrayList<>();

    public InnerClassesAttribute(String name){
        super();
        super.setAttribute_name(name);
    }

    @Override
    public void parse(DataInputStream dis, ClassInfo ci) {
        this.setCi(ci);
        try {
            super.attribute_length = Parse.byteToInt(super.attribute_length_u4, dis);
            this.number_of_classes = Parse.byteToInt(this.number_of_classes_u2, dis);

            int i = 0;
            while (i < this.number_of_classes){
                Inner inner = new Inner();
                inner.parse(dis,ci);
                this.inners.add(inner);
                i++;
            }
        } catch (IOException e) {
            log.error(e);
        }
    }

    @Override
    public String print(String print) {
        StringBuilder builder = new StringBuilder();
        builder.append("InnerClasses").append(PrintFormat.COLON).append(System.lineSeparator());
        for (Inner inner : this.inners) {
            builder.append(inner.print(print));
        }

        return builder.toString();
    }

    @Data
    @Log4j2
    public static class Inner implements Parse{
        private int inner_class_info_index_u2 = 2;
        private int inner_class_info_index;

        private int outer_class_info_index_u2 = 2;
        private int outer_class_info_index;

        private int inner_name_index_u2 = 2;
        private int inner_name_index;

        private int inner_class_access_flags_u2 = 2;
        private int inner_class_access_flags;

        private ClassInfo ci;

        private String inner;
        private String outer;
        private String name;

        @Override
        public void parse(DataInputStream dis, ClassInfo ci) {
            this.setCi(ci);
            try {
                this.inner_class_info_index = Parse.byteToInt(this.inner_class_info_index_u2, dis);
                this.outer_class_info_index = Parse.byteToInt(this.outer_class_info_index_u2, dis);
                this.inner_name_index = Parse.byteToInt(this.inner_name_index_u2, dis);
                this.inner_class_access_flags = Parse.byteToInt(this.inner_class_access_flags_u2, dis);
            } catch (IOException e) {
                log.error(e);
            }
        }

        public String getInner() {
            if(Objects.nonNull(this.inner)){
                return this.inner;
            }

            ConstantClassInfo cci = (ConstantClassInfo)this.getCi().getCp_info().getPools().get(String.valueOf(this.inner_class_info_index));
            this.inner = cci.getConstant();

            return inner;
        }

        public String getOuter() {
            if(Objects.nonNull(this.outer)){
                return this.outer;
            }

            ConstantClassInfo cci = (ConstantClassInfo)this.getCi().getCp_info().getPools().get(String.valueOf(this.outer_class_info_index));
            this.outer = cci.getConstant();

            return outer;
        }

        public String getName() {
            if(this.inner_name_index == 0){
                return "";
            }
            if(Objects.nonNull(this.name)){
                return this.name;
            }

            ConstantUtf8Info cui = (ConstantUtf8Info)this.getCi().getCp_info().getPools().get(String.valueOf(this.inner_name_index));
            this.name = cui.getConstant();

            return name;
        }

        public String print(String print){
            StringBuilder builder = new StringBuilder();
            builder.append(print).append(PrintFormat.EMPTY);
            List<TypeFatory.Access> accesses = TypeFatory.access_flags(this.inner_class_access_flags);
            for (TypeFatory.Access access : accesses) {
                builder.append(access.getName()).append(PrintFormat.EMPTY);
            }
            if(this.outer_class_info_index ==0){
                builder.append(PrintFormat.HASHTAG).append(this.inner_class_info_index).append(PrintFormat.SEMICOLON);
                builder.append(PrintFormat.EMPTY)
                        .append(PrintFormat.DOUBLE_SLASH)
                        .append("class")
                        .append(PrintFormat.EMPTY)
                        .append(this.getInner());
            }else{
                builder.append(PrintFormat.HASHTAG)
                        .append(this.inner_name_index)
                        .append(PrintFormat.EQUAL)
                        .append(PrintFormat.HASHTAG)
                        .append(this.inner_class_info_index)
                        .append(PrintFormat.EMPTY)
                        .append("of")
                        .append(PrintFormat.EMPTY)
                        .append(PrintFormat.HASHTAG)
                        .append(this.outer_class_info_index)
                        .append(PrintFormat.SEMICOLON);
                builder.append(PrintFormat.EMPTY)
                        .append(PrintFormat.DOUBLE_SLASH)
                        .append("class")
                        .append(PrintFormat.EMPTY)
                        .append(this.getName())
                        .append(PrintFormat.EQUAL)
                        .append(this.getInner())
                        .append(PrintFormat.EMPTY)
                        .append("of")
                        .append(PrintFormat.EMPTY)
                        .append("class")
                        .append(PrintFormat.EMPTY)
                        .append(this.getOuter());
            }
            builder.append(System.lineSeparator());

            return builder.toString();
        }

    }
}
